﻿using System.Data;
using System.Diagnostics;
using System.Linq.Expressions;
using BLToolkit.Data;
using Rsdn.Janus.DataModel;
using Rsdn.Janus.ObjectModel;

namespace Rsdn.Janus
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using BLToolkit.Data.Linq;
    using JetBrains.Annotations;
    using SmartApp;

    /// <summary>
    /// Вспомогательные методы для работы с форумом.
    /// </summary>
    public static class ForumHelper
    {
        /// <summary>
        /// Gets the formatter.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"><paramref name="provider"/> is <c>null</c>.</exception>
        public static IJanusFormatter GetFormatter([NotNull] this IServiceProvider provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            return provider.GetRequiredService<IJanusFormatter>();
        }

        /// <summary>
        /// Gets the display name.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="descript">The descript.</param>
        /// <returns></returns>
        public static string GetDisplayName(string name, string descript)
        {
            return Config.Instance.ForumDisplayConfig.ShowFullForumNames ? descript : name;
        }

        /// <summary>
        /// Marks the MSG read.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="messages">The messages.</param>
        /// <param name="isRead">if set to <c>true</c> [is read].</param>
        /// <param name="withSubnodes">if set to <c>true</c> [with subnodes].</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void MarkMsgRead(
            [NotNull] IServiceProvider provider,
            IEnumerable<MsgBase> messages,
            bool isRead,
            bool withSubnodes)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (messages == null || !messages.Any())
            {
                return;
            }

#if FRM_UPDATE_TRACE
            Trace.Write(String.Format("Frm: MarkMsgsRead and update related forums"));
#endif

            IEventBroker eventBroker = provider.GetRequiredService<IEventBroker>();

            IEnumerable<MsgBase> enumerable;
            if (withSubnodes)
            {
                IEnumerable<MsgBase> markedMsgList = GetMarkedMsgList(messages, isRead);
                enumerable = markedMsgList.Distinct(MsgBaseComparer.Instance);
            }
            else
            {
                enumerable = messages.Where(msg => msg.IsUnread == isRead);
            }

            MsgBase[] msgs = enumerable.ToArray();

            ForumEntryIds[] msgIds = msgs
                .Select(msg => new ForumEntryIds(msg.ForumID, msg.TopicID, msg.ID))
                .ToArray();

            ForumEntryChangedEventArgs forumEntryChangedEventArgs =
                new ForumEntryChangedEventArgs(msgIds, ForumEntryChangeType.ReadMark);

            eventBroker.Fire(ForumEventNames.BeforeForumEntryChanged, forumEntryChangedEventArgs);

            IMessageMarkService messageMarkService = provider.GetRequiredService<IMessageMarkService>();
            IIncomingFolderUpdate folderUpdate = (IIncomingFolderUpdate)provider.GetService(typeof(IIncomingFolderUpdate));
            IEnumerable<Feature> updates = folderUpdate.GetUpdates;

            // Note :*** Here we must update many places
            Action actionUpdateForums =
                delegate
                {
                    // Обновляем агрегатную информацию об изменившихся форумах.
                    IEnumerable<Forum> forums = msgs
                        .Select(msg => msg.ForumID)
                        .Distinct()
                        .Select(id => Forums.Instance[id]);

                    foreach (Forum forum in forums)
                    {
                        // non active forums must be read again for update read state
                        if (Features.Instance.ActiveFeature == forum)
                        {
                            forum.RefreshInfo();
                        }
                        else
                        {
                            forum.Refresh();
                        }
                    }

                    // update virtual forums
                    foreach (Feature feature in updates)
                    {
                        if (feature is Forum)
                        {
                            Forum forum = (Forum) feature;

                            // non active forums must be read again for update read state
                            if (Features.Instance.ActiveFeature == feature)
                            {
                                forum.RefreshInfo();
                            }
                            else
                            {
                                forum.Refresh();
                            }
                        }
                    }
                    eventBroker.Fire(ForumEventNames.AfterForumEntryChanged, forumEntryChangedEventArgs);
                };

            messageMarkService.QueueMessageMark(msgIds, isRead, actionUpdateForums);

            foreach (MsgBase msg in msgs)
            {
                if (msg.IsUnread == isRead)
                {
                    msg.SetUnread(!isRead, Config.Instance.SelfId);
                }
            }
            IUIShell uiShell = provider.GetRequiredService<IUIShell>();
            uiShell.RefreshUI();
#if FRM_UPDATE_TRACE
            Trace.WriteLine("");
#endif
        }

       /// <summary>
        /// Sets the forums read mark.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="forums">The forums.</param>
        /// <param name="isRead">if set to <c>true</c> [is read].</param>
        /// <exception cref="ArgumentNullException"><paramref name="provider" /> is <c>null</c>.</exception>
        public static void SetForumsReadMark([NotNull] IServiceProvider provider, IEnumerable<IForum> forums, bool isRead)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (forums == null || !forums.Any())
            {
                return;
            }

            IEventBroker eventBroker = provider.GetRequiredService<IEventBroker>();

            IForum[] forumsToMark = forums
                .Distinct()
                .Where(forum => forum.CanSetForumReadMark(isRead))
                .ToArray();

            ForumEntryIds[] forumsIds = forumsToMark
                .Select(forum => new ForumEntryIds(forum.ID))
                .ToArray();

            ForumEntryChangedEventArgs forumEntryChangedEventArgs = 
                new ForumEntryChangedEventArgs(forumsIds, ForumEntryChangeType.ReadMark);

            eventBroker.Fire(ForumEventNames.BeforeForumEntryChanged, forumEntryChangedEventArgs);

            foreach (Forum forum in forumsToMark)
            {
                DatabaseManager.MarkMessagesReadByForumId(provider, forum.ID, isRead, Config.Instance.SelfId);
                forum.Refresh();
            }

            eventBroker.Fire(ForumEventNames.AfterForumEntryChanged, forumEntryChangedEventArgs);

            IUIShell uiShell = provider.GetRequiredService<IUIShell>();
            uiShell.RefreshUI();
        }

        /// <summary>
        /// Marks the older than.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="beforeDate">The before date.</param>
        /// <param name="afterDate">The after date.</param>
        /// <param name="exceptAnswersMe">if set to <c>true</c> [except answers me].</param>
        /// <param name="forumsIds">The forums ids.</param>
        /// <param name="asRead">if set to <c>true</c> [as read].</param>
        /// <returns></returns>
        private static int MarkOlderThan(
            IServiceProvider provider,
            DateTime? beforeDate,
            DateTime? afterDate,
            bool exceptAnswersMe,
            IEnumerable<int> forumsIds,
            bool asRead)
        {
            // Пометка сообщений. В два этапа. Сначала помечаем,
            // затем пересчёт агрегатов.
            //
			using (var db = provider.CreateDBContext())
            {
			using (var tx = db.BeginTransaction())
                {
                    var forumMessages = db.Messages(m => m.IsRead != asRead);

                    if (beforeDate.HasValue)
                    {
                        DateTime beforeDateParam =
                            new DateTime(
                                beforeDate.Value.Year,
                                beforeDate.Value.Month,
                                beforeDate.Value.Day,
                                beforeDate.Value.Hour,
                                beforeDate.Value.Minute,
                            // Если миллисекунды != 0, то вылетает исключение(OleDBException) - неверный тип, поэтому отрезаем их
                                0);

                        forumMessages = forumMessages.Where(m => m.Date < beforeDateParam);
                    }

                    if (afterDate.HasValue)
                    {
                        var afterDateParam =
                            new DateTime(
                                afterDate.Value.Year,
                                afterDate.Value.Month,
                                afterDate.Value.Day,
                                afterDate.Value.Hour,
                                afterDate.Value.Minute,
                            // Если миллисекунды != 0, то вылетает исключение(OleDBException) - неверный тип, поэтому отрезаем их
                                0);

                        forumMessages = forumMessages.Where(m => m.Date > afterDateParam);
                    }

                    if (forumsIds.Any())
                    {
                        forumMessages = forumMessages.Where(m => forumsIds.Contains(m.ForumID));
                    }

                    if (exceptAnswersMe)
                    {
                        forumMessages = forumMessages.Where(m => m.Parent.UserID == Config.Instance.SelfId);
                    }

                    // Akasoft: Вот тут я удавился, добавляя параметром @uid. Пришлось заменить на String.Format(). 
                    // 2 вечера отладки, полная фигня 2, ведь ниже-то всё работает...

                    // Обновление агрегатов делаем в два этапа.
                    // В начале просто сбрасываем все данные о прочтении в topic_info
                    // для всех указанных форумов.
                    // Затем смотрим в тех же форумах, есть ли в них непрочитанные сообщения
                    // и сколько.
                    // По полученным данным обновляем topic_info.
                    var res =
                        forumMessages
                            .Set(_ => _.IsRead, () => asRead)
                            .Update();

                    if (res == 0)
                    {
                        return 0;
                    }

                    Expression<Func<ITopicInfo, int>> expressionAnswersUnread = _ => _.AnswersUnread;

                    Expression<Func<ITopicInfo, int>> expressionAnswersToMeUnread = _ => _.AnswersToMeUnread;

                    db
                        .TopicInfos(ti => forumsIds.Contains(ti.ForumID))
                            .Set(expressionAnswersUnread, 0)
                            .Set(expressionAnswersToMeUnread, 0)
                        .Update();

                    IQueryable<IForumMessage> messages = db.Messages(m => m.TopicID == 0);
                    if (forumsIds != null)
                    {
                        if (forumsIds.Any())
                        {
                            messages = messages.Where(m => forumsIds.Contains(m.ForumID));
                        }
                    }

                    var q2 =
                        from forumMessage in messages
                        join mb in
                            (from message1 in db.Messages()
                             where message1.TopicID != 0 && !message1.IsRead
                             group message1 by message1.TopicID
                                 into grp
                                 select new { TopicID = grp.Key, AnswersUnread = grp.Count() })
                            on forumMessage.ID equals mb.TopicID into ljb
                        from mb in ljb
                        join mc in
                            (from message2 in db.Messages()
                             where
                                 message2.TopicID != 0
                                 && !message2.IsRead
                                 && message2.Parent.UserID == Config.Instance.SelfId
                             group message2 by message2.TopicID
                                 into grp
                                 select new { TopicID = grp.Key, AnswersMeUnread = grp.Count() })
                            on forumMessage.ID equals mc.TopicID into ljc
                        from mc in ljc
                        where !forumMessage.IsRead || mb.AnswersUnread > 0
                        select new { forumMessage.ID, mb.AnswersUnread, mc.AnswersMeUnread };

                    var topics = q2.ToList();

                    foreach (var item in topics)
                    {
                        var localItem = item;
                        db
                            .TopicInfos(ti => ti.MessageID == localItem.ID)
                                .Set(expressionAnswersUnread, () => localItem.AnswersUnread)
                                .Set(expressionAnswersToMeUnread, () => localItem.AnswersMeUnread)
                            .Update();
                    }

                    tx.Commit();
                    return res;
                }
            }
        }

        /// <summary>
        /// Marks the messages by date.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="forumIds">The forum ids.</param>
        /// <param name="isRead">if set to <c>true</c> [is read].</param>
        /// <param name="startDate">The start date.</param>
        /// <param name="endDate">The end date.</param>
        /// <param name="exceptAnswersMe">if set to <c>true</c> [except answers me].</param>
        /// <returns></returns>
        public static int MarkMessagesByDate(
            [NotNull] IServiceProvider provider,
            IEnumerable<int> forumIds,
            bool isRead,
            DateTime? startDate,
            DateTime? endDate,
            bool exceptAnswersMe)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            IEventBroker eventBroker = provider.GetRequiredService<IEventBroker>();

            ForumEntryIds[] forumEntryIds = forumIds != null && forumIds.Any()
                ? forumIds.Select(forumId => new ForumEntryIds(forumId)).ToArray()
                : new[] { ForumEntryIds.AllForums };

            ForumEntryChangedEventArgs forumEntryChangedEventArgs =
                new ForumEntryChangedEventArgs(forumEntryIds, ForumEntryChangeType.ReadMark);

            eventBroker.Fire(ForumEventNames.BeforeForumEntryChanged, forumEntryChangedEventArgs);

            IEnumerable<int> forumsIds = forumIds != null ? forumIds : Enumerable.Empty<int>();

            int marksCount = MarkOlderThan(provider, startDate, endDate, exceptAnswersMe, forumsIds, isRead);

            IEnumerable<Forum> forums;
            if (forumIds != null && forumIds.Any())
            {
                forums = forumIds.Select(forumId => Forums.Instance[forumId]);
            }
            else
            {
                forums = Forums.Instance.ForumList;
            }

            forums.ForEach(forum => forum.Refresh());

            //(forumIds != null && forumIds.Any()
            //        ? forums
            //        : Forums.Instance.ForumList)
            //    .ForEach(forum => forum.Refresh());

            eventBroker.Fire(ForumEventNames.AfterForumEntryChanged, forumEntryChangedEventArgs);

            return marksCount;
        }

        /// <summary>
        /// Помечает сообщения до <paramref name="maxId"/> прочитанными,
        /// затем снимает флаг прочтения с укзанных сообщений.
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="maxId">Максимальный ID сообщения, 
        /// который учавствует в выборке.</param>
        /// <param name="unreadIds">Массив ID сообщений, с которых следует 
        /// снять флаг прочитанности</param>
        private static void MarkMessagesRead(IServiceProvider provider, int maxId, IEnumerable<int> unreadIds)
        {
			using (var db = provider.CreateDBContext())
			using (var tx = db.BeginTransaction())
            {
                var affectedIds = new HashSet<int>(
                    db
                        .Messages(m => m.ID < maxId + 1 && !m.IsRead)
                        .Select(m => m.ID)
                        .AsEnumerable()
                        .Concat(unreadIds));

                // все сообщения до maxId прочитанныe
                db
                    .Messages(m => !m.IsRead && m.ID < maxId + 1)
                    .Set(_ => _.IsRead, true)
                    .Update();

                // указанные сообщения не прочитанныe
                foreach (var series in unreadIds.SplitForInClause(provider))
                {
                    var locSeries = series;
                    db
                        .Messages(m => locSeries.Contains(m.ID))
                        .Set(_ => _.IsRead, false)
                        .Update();
                }

                // обновляем агрегатную информацию
                DatabaseManager.UpdateTopicInfoSpecified(provider, db, affectedIds, Config.Instance.SelfId);

                tx.Commit();
            }
        }

        /// <summary>
        /// Marks the messages by id.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="unreadMsgIds">The unread MSG ids.</param>
        /// <param name="maxId">The max id.</param>
        public static void MarkMessagesById([NotNull] IServiceProvider provider, IEnumerable<int> unreadMsgIds, int maxId)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (unreadMsgIds == null || !unreadMsgIds.Any())
            {
                return;
            }

            var eventBroker = provider.GetRequiredService<IEventBroker>();
            var forumEntryIds = new[] { ForumEntryIds.AllForums };

            ForumEntryChangedEventArgs forumEntryChangedEventArgs = new ForumEntryChangedEventArgs(forumEntryIds, ForumEntryChangeType.ReadMark);

            IIncomingFolderUpdate folderUpdate = (IIncomingFolderUpdate)provider.GetService(typeof(IIncomingFolderUpdate));
            IEnumerable<Feature> updates = folderUpdate.GetUpdates;

            eventBroker.Fire(ForumEventNames.BeforeForumEntryChanged, forumEntryChangedEventArgs);

            MarkMessagesRead(provider, maxId, unreadMsgIds);

            foreach (var forum in Forums.Instance.ForumList)
            {
                forum.Refresh();
            }

            foreach (Feature feature in updates)
            {
                if (feature is Forum)
                {
                    Forum forum = (Forum)feature;
                    forum.RefreshInfo();
                }
            }

            eventBroker.Fire(ForumEventNames.AfterForumEntryChanged, forumEntryChangedEventArgs);
        }

        /// <summary>
        /// Sets the MSG group read replies.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="mids">The mids.</param>
        /// <param name="readReplies">if set to <c>true</c> [read replies].</param>
        private static void SetMsgGroupReadReplies(IServiceProvider provider, IEnumerable<int> mids, bool readReplies)
        {
            if (mids == null)
            {
                throw new ArgumentNullException("mids");
            }

			using (var db = provider.CreateDBContext())
			using (var tx = db.BeginTransaction())
            {
                foreach (var ids in mids.SplitToSeries(provider.MaxInClauseElements()))
                {
                    var localIds = ids;
                    db
                        .Messages(m => localIds.Contains(m.ID))
                        .Set(_ => _.ReadReplies, readReplies)
                        .Update();
                }
                tx.Commit();
            }
        }

        /// <summary>
        /// Sets the message replies auto read mark.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="messages">The messages.</param>
        /// <param name="isEnabled">if set to <c>true</c> [is enabled].</param>
        public static void SetMessageRepliesAutoReadMark(
            [NotNull] IServiceProvider provider,
            IEnumerable<MsgBase> messages,
            bool isEnabled)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (messages == null || !messages.Any())
            {
                return;
            }

            var eventBroker = provider.GetRequiredService<IEventBroker>();

            var msgs = GetReadRepliesMessages(messages, isEnabled, true).ToArray();
            var msgIds = msgs.Select(msg => new ForumEntryIds(msg.ForumID, msg.TopicID, msg.ID));

            eventBroker.Fire(
                ForumEventNames.BeforeForumEntryChanged,
                new ForumEntryChangedEventArgs(msgIds, ForumEntryChangeType.AutoReadMark));

            SetMsgGroupReadReplies(
                provider,
                msgs.Select(msg => msg.ID),
                isEnabled);

            eventBroker.Fire(
                ForumEventNames.AfterForumEntryChanged,
                new ForumEntryChangedEventArgs(msgIds, ForumEntryChangeType.AutoReadMark));
        }

        /// <summary>
        /// Determines whether this instance [can set forum read mark] the specified forum.
        /// </summary>
        /// <param name="forum">The forum.</param>
        /// <param name="isRead">if set to <c>true</c> [is read].</param>
        /// <returns>
        /// 	<c>true</c> if this instance [can set forum read mark] the specified forum; otherwise, <c>false</c>.
        /// </returns>
        public static bool CanSetForumReadMark(this IForum forum, bool isRead)
        {
            return isRead ? forum.Unread > 0 : forum.Unread < forum.MessagesCount;
        }

        /// <summary>
        /// Ищем первое непрочитанное сообщение от текущего.
        /// Если ниже текущего нет непрочитанных, пытаемся искать от корня.
        /// </summary>
        /// <param name="forum"><see cref="Forum"/>.</param>
        /// <param name="skipActiveUnread">Флаг, указывающий стоит ли
        /// пропускать текущее сообщение в заданном форуме, если оно не прочитано.</param>
        /// <returns>
        /// Непрочитанное сообщение <see cref="IMsg"/> или <c>null, если сообщение не найдено.</c>
        /// </returns>
        public static IMsg FindNextUnreadMsg(this Forum forum, bool skipActiveUnread)
        {
            return skipActiveUnread || !(forum.ActiveMsg != null && forum.ActiveMsg.IsUnread)
                       ? forum.FindNextUnreadMsg()
                       : forum.ActiveMsg;
        }

        /// <summary>
        /// Ищем первое непрочитанное сообщение от текущего.
        /// Если ниже текущего нет непрочитанных, пытаемся искать от корня.
        /// </summary>
        /// <param name="forum"><see cref="Forum"/>.</param>
        /// <returns>
        /// Непрочитанное сообщение <see cref="IMsg"/> или <c>null, если сообщение не найдено.</c>
        /// </returns>
        private static IMsg FindNextUnreadMsg(this Forum forum)
        {
            if (forum.Unread == 0 || forum.MessagesCount == 0)
            {
                return null;
            }

            Func<IMsg, bool> msgMatch = msg => msg.IsUnread;
            Func<IMsg, bool> topicMatch = msg => msg.RepliesUnread != 0 || msg.IsUnread;

            var activeMsg = forum.ActiveMsg;

            // Ищем от в текущем топике...
            if (activeMsg != null && activeMsg.Topic.RepliesUnread != 0)
            {
                var message = activeMsg.Topic
                    .Flatten(activeMsg).FirstOrDefault(msgMatch);

                if (message != null)
                {
                    return message;
                }
            }

            // Доступные сообщения
            var root = forum.Msgs.Cast<IMsg>();

            // Если сообщения форума загружены не полностью, 
            // делаем две попытки поиска
            for (; ; )
            {
                // Создаем последовательность от текущего топика (не включая) до конца,
                // от начала до текущего, включительно
                var msgs =
                    activeMsg == null
                        ? root
                        : root
                            .SkipWhile(msg => msg != activeMsg.Topic)
                            .SkipWhile(msg => msg == activeMsg.Topic)
                            .Concat(root.TakeWhile(msg => msg != activeMsg.Topic))
                            .Concat(new[] { activeMsg.Topic });

                // Ищем топик...
                var message = msgs.FirstOrDefault(topicMatch);

                // Ищем непрочитанное сообщение...
                if (message != null)
                {
                    return message.Flatten().First(msgMatch);
                }

                // если не нашли, но все сообщения загружены полностью
                if (forum.IsAllMsgLoaded)
                {
                    return null;
                }

                // загружаем все сообщения и повторяем поиск
                root = forum.LoadAllMsg().Cast<IMsg>();
            }
        }

        /// <summary>
        /// Flattens the specified root.
        /// </summary>
        /// <param name="root">The root.</param>
        /// <returns></returns>
        public static IEnumerable<IMsg> Flatten(this IMsg root)
        {
            yield return root;

            foreach (IMsg msg in root)
            {
                foreach (var child in Flatten(msg))
                {
                    yield return child;
                }
            }
        }

        /// <summary>
        /// Flattens the specified root.
        /// </summary>
        /// <param name="root">The root.</param>
        /// <param name="startBeyond">The start beyond.</param>
        /// <returns></returns>
        private static IEnumerable<IMsg> Flatten(this IMsg root, IMsg startBeyond)
        {
            return Flatten(root).SkipWhile(msg => msg != startBeyond).Skip(1);
        }

        /// <summary>
        /// Gets the marked MSG list.
        /// </summary>
        /// <param name="msgs">The MSGS.</param>
        /// <param name="markForRead">if set to <c>true</c> [mark for read].</param>
        /// <returns></returns>
        private static IEnumerable<MsgBase> GetMarkedMsgList(IEnumerable<MsgBase> msgs, bool markForRead)
        {
            foreach (Msg msg in msgs)
            {
                if (msg.IsUnread == markForRead)
                {
                    yield return msg;
                }

                if (msg.HasChildren)
                {
                    // Сколько у сообщения непрочитанных детей?
                    var unreadedChildsCount = msg.RepliesUnread;

                    // Минус себя
                    if (msg.IsUnread)
                    {
                        unreadedChildsCount--;
                    }

                    // Все дочерние непрочитаны
                    var allUnread = (msg.RepliesCount == unreadedChildsCount);

                    // Все дочерние прочитаны
                    var allRead = (unreadedChildsCount == 0);

                    if (markForRead && !allRead || !markForRead && !allUnread)
                    {
                        foreach (var child in GetMarkedMsgList(msg, markForRead))
                        {
                            yield return child;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets the read replies messages.
        /// </summary>
        /// <param name="messages">The messages.</param>
        /// <param name="readReplies">if set to <c>true</c> [read replies].</param>
        /// <param name="withSubnodes">if set to <c>true</c> [with subnodes].</param>
        /// <returns></returns>
        private static IEnumerable<MsgBase> GetReadRepliesMessages(IEnumerable<MsgBase> messages, bool readReplies, bool withSubnodes)
        {
            foreach (var message in messages)
            {
                if (message.ReadReplies != readReplies)
                {
                    message.ReadReplies = readReplies;
                    yield return message;
                }

                if (withSubnodes && message.HasChildren)
                {
                    foreach (var child in GetReadRepliesMessages(message, readReplies, true))
                    {
                        yield return child;
                    }
                }
            }
        }
    }
}
